系列文章: 前端工程師的 Modern Web 實踐之道 - Day 3
預計閱讀時間: 10 分鐘
難度等級: ⭐⭐⭐☆☆
在前一篇文章中,我們建立了現代化的開發環境基礎。今天我們將深入探討包管理器的選擇策略,這個決定將直接影響你的開發效率、專案性能和團隊協作體驗。
在現代前端開發中,一個典型專案的 node_modules
可能包含數百個相依套件,佔用數百 MB 的硬碟空間。每當我們執行 npm install
時,背後發生的不只是檔案下載,還涉及:
// package.json - npm 專案配置
{
"name": "modern-web-project",
"version": "1.0.0",
"dependencies": {
"react": "^18.2.0",
"typescript": "^5.0.0"
},
"engines": {
"npm": ">=8.0.0",
"node": ">=16.0.0"
}
}
技術特點:
# .yarnrc.yml - Yarn 3 配置
nodeLinker: pnp # 使用 Plug'n'Play 模式
enableGlobalCache: true
compressionLevel: mixed
技術創新:
// .pnpmfile.cjs - pnpm 配置範例
module.exports = {
hooks: {
readPackage(pkg, context) {
// 自訂相依性解析邏輯
if (pkg.name === 'some-package') {
pkg.dependencies = {
...pkg.dependencies,
'optimized-dep': '^2.0.0'
}
}
return pkg
}
}
}
核心優勢:
// bun.sh 安裝與使用
$ curl -fsSL https://bun.sh/install | bash
$ bun install # 套件安裝
$ bun run dev # 執行腳本
$ bun build # 應用程式建置
# bunfig.toml - Bun 設定檔
[install]
# 設定 npm 註冊表
registry = "https://registry.npmjs.org/"
# 啟用快取
cache = true
# 安裝時自動信任憑證
auto = true
[install.scopes]
# 私有套件範圍設定
"@mycompany" = "https://npm.internal.com/"
革命性特點:
讓我們用一個實際的 React + TypeScript 專案來測試各個包管理器的表現:
// 測試專案 package.json
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@types/react": "^18.0.0",
"typescript": "^5.0.0",
"vite": "^4.0.0",
"tailwindcss": "^3.2.0",
"framer-motion": "^10.0.0",
"react-query": "^3.39.0",
"zustand": "^4.3.0"
}
}
#!/bin/bash
# benchmark.sh - 包管理器性能測試
test_manager() {
local manager=$1
local project_path="./test-${manager}"
echo "Testing $manager..."
# 清理環境
rm -rf $project_path
mkdir $project_path
cd $project_path
# 複製 package.json
cp ../package.json .
# 測量安裝時間
start_time=$(date +%s%N)
case $manager in
"npm")
npm install --no-audit --no-fund
;;
"yarn")
yarn install --frozen-lockfile
;;
"pnpm")
pnpm install --frozen-lockfile
;;
"bun")
bun install
;;
esac
end_time=$(date +%s%N)
duration=$(( (end_time - start_time) / 1000000 ))
# 測量硬碟使用量
disk_usage=$(du -sh node_modules 2>/dev/null | cut -f1)
echo "$manager: ${duration}ms, Disk: $disk_usage"
cd ..
}
# 執行測試
for manager in npm yarn pnpm bun; do
test_manager $manager
done
// 測試結果處理腳本
const benchmarkResults = {
npm: {
installTime: 45230, // ms
diskUsage: '156MB',
features: ['npx', 'workspaces', 'audit']
},
yarn: {
installTime: 32100, // ms
diskUsage: '142MB',
features: ['pnp', 'zero-installs', 'berry']
},
pnpm: {
installTime: 28750, // ms
diskUsage: '89MB', // 符號連結節省空間
features: ['strict-isolation', 'monorepo', 'shamefully-hoist']
},
bun: {
installTime: 18920, // ms - 最快速度
diskUsage: '145MB', // 傳統 node_modules 結構
features: ['native-runtime', 'zero-config-ts', 'built-in-bundler', 'web-api']
}
};
function analyzeResults(results) {
const fastest = Object.entries(results)
.sort(([,a], [,b]) => a.installTime - b.installTime)[0];
const mostEfficient = Object.entries(results)
.sort(([,a], [,b]) =>
parseInt(a.diskUsage) - parseInt(b.diskUsage))[0];
console.log(`Fastest: ${fastest[0]} (${fastest[1].installTime}ms)`);
console.log(`Most efficient: ${mostEfficient[0]} (${mostEfficient[1].diskUsage})`);
}
analyzeResults(benchmarkResults);
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
// apps/web/package.json
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui-components": "workspace:*",
"@myorg/shared-utils": "workspace:^1.0.0"
}
}
// 跨套件依賴管理腳本
// scripts/sync-versions.js
const fs = require('fs');
const path = require('path');
const glob = require('glob');
function syncWorkspaceVersions() {
const workspaces = glob.sync('./packages/*/package.json');
const versions = new Map();
// 收集所有內部套件版本
workspaces.forEach(packagePath => {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
versions.set(pkg.name, pkg.version);
});
// 更新跨套件相依性
workspaces.forEach(packagePath => {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
let updated = false;
['dependencies', 'devDependencies'].forEach(depType => {
if (pkg[depType]) {
Object.keys(pkg[depType]).forEach(depName => {
if (versions.has(depName) &&
pkg[depType][depName].startsWith('workspace:')) {
pkg[depType][depName] = `workspace:^${versions.get(depName)}`;
updated = true;
}
});
}
});
if (updated) {
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2));
console.log(`Updated ${pkg.name}`);
}
});
}
syncWorkspaceVersions();
// package.json - Bun workspaces 設定
{
"name": "my-monorepo",
"workspaces": ["apps/*", "packages/*"],
"devDependencies": {
"bun-types": "latest"
}
}
// bun 專案中的 TypeScript 設定範例
// apps/web/src/index.ts
import { apiClient } from '@myorg/shared-api';
import { Button } from '@myorg/ui-components';
// Bun 原生支援 TypeScript,無需額外設定
const app = {
async start() {
const data = await apiClient.fetchUsers();
console.log('Users loaded:', data.length);
}
};
// 使用 Bun 的內建 Web API
export default {
port: 3000,
fetch(request: Request) {
return new Response("Hello from Bun!");
},
};
# Bun monorepo 常用指令
$ bun install # 安裝所有依賴
$ bun --filter web run dev # 在特定 workspace 執行指令
$ bun run build --filter @myorg/ui-components # 建置特定套件
# .npmrc - 企業級安全配置
registry=https://registry.npmjs.org/
audit-level=moderate
fund=false
package-lock-only=true
# 私有套件配置
@mycompany:registry=https://npm.internal.com/
//npm.internal.com/:_authToken=${NPM_INTERNAL_TOKEN}
# 安全性設定
ignore-scripts=true # 禁止自動執行安裝腳本
# .github/workflows/security-audit.yml
name: Security Audit
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install pnpm
run: npm install -g pnpm
- name: Security Audit
run: |
pnpm audit --audit-level moderate
pnpm licenses list --json > licenses.json
- name: Check License Compliance
run: |
node scripts/check-licenses.js
// scripts/check-licenses.js - 授權合規檢查
const fs = require('fs');
const allowedLicenses = [
'MIT', 'Apache-2.0', 'BSD-2-Clause',
'BSD-3-Clause', 'ISC', 'CC0-1.0'
];
const prohibitedLicenses = [
'GPL-2.0', 'GPL-3.0', 'AGPL-3.0', 'LGPL-2.1', 'LGPL-3.0'
];
function checkLicenses() {
const licenses = JSON.parse(fs.readFileSync('licenses.json', 'utf8'));
const violations = [];
Object.entries(licenses).forEach(([packageName, info]) => {
const license = info.license;
if (prohibitedLicenses.includes(license)) {
violations.push({
package: packageName,
license,
severity: 'error'
});
} else if (!allowedLicenses.includes(license)) {
violations.push({
package: packageName,
license,
severity: 'warning'
});
}
});
if (violations.length > 0) {
console.log('License violations found:');
violations.forEach(v => {
console.log(`${v.severity.toUpperCase()}: ${v.package} (${v.license})`);
});
const errors = violations.filter(v => v.severity === 'error');
if (errors.length > 0) {
process.exit(1);
}
} else {
console.log('All licenses compliant ✅');
}
}
checkLicenses();
.nvmrc
和 package.json
中明確指定工具版本.gitignore